#ifndef __EPSILON_3D__DRIVER_H__
#define __EPSILON_3D__DRIVER_H__

#include "OpenGL/OpenGL.h"
#include "AGL/agl.h"

///#include <GL/gl.h>
///#include <GL/glext.h>

#include <stdlib.h>
#include <stdio.h>

#include <string.h>

#include <vector>

class e3dRenderState;

namespace Epsilon3D
{
  enum State
	{
		Lighting,
		DepthTest,

		NumberOfStates
	};

	class Light
	{
		public:
			         Light (int id) : id_ (id), enabled_ (false) { };
			virtual ~Light (void) { };

		  /// TODO: Consider passing a Driver object, so that calling these may
			///        actually enable and disable the lights, rather than changing
			///        the internally stored state.
			virtual void Enable  (void) { enabled_ = true;  };
			virtual void Disable (void) { enabled_ = false; };

		private:
			int  id_;
			bool enabled_;
	};


	class Driver
	{
		class FrameBufferSpecs
		{
			public:
				GLint depth_bits_;
				GLint stencil_bits_;

				GLint red_bits_;
				GLint green_bits_;
				GLint blue_bits_;
				GLint alpha_bits_;
		};

		struct VertexBufferSpecs
		{
			GLint max_elements_;	/// Not an absolute max, more of a suggested max
			GLint max_vtx_attribs_;
		};

		struct IndexBufferSpecs
		{
			GLint max_elements_;
		};

		public:
			         Driver (void) { };
			virtual ~Driver (void) { };

			virtual void Init (void) = 0;  ///< XXX

			virtual void EnableState  (State name) = 0;
			virtual void DisableState (State name) = 0;

			virtual void   EnableLight  (int iLight) = 0;
			virtual void   DisableLight (int iLight) = 0;
			virtual Light* GetLight     (int iLight) {
				// Assert (iLight < max_hw_lights_)
				return lights_ [iLight];
			}

			virtual e3dRenderState* States (void) { return states_; };

		protected:
			GLint max_tex_size_;
			GLint max_hw_lights_;
			GLint max_clip_planes_;

			GLint num_tex_samplers_;

			FrameBufferSpecs  fb_info_;
			VertexBufferSpecs vb_info_;
			IndexBufferSpecs  ib_info_;

			std::vector<Light *> lights_;

			e3dRenderState* states_;
	};

namespace OpenGL
{
	class Extension
	{
		public:
			         Extension (const char* szName) {
				if (szName != NULL) {
					name_ = new char [strlen (szName) + 1];
					strcpy (name_, szName);
				}
			}

			virtual ~Extension (void) {
				if (name_ != NULL) {
					delete [] name_;
					name_ = NULL;
				}
			};

			virtual bool Init (void) = 0;

		private:
			char* name_;
	};

	class VertexBufferObject : public Extension
	{
		friend class ExtList;

		protected:
			 VertexBufferObject (void) : Extension ("GL_ARB_Vertex_Buffer_Object") { };
			~VertexBufferObject (void);

		private:
	};

  class ExtList
	{
		public:
			bool isExtensionSupported (const char* szExtension) const;

		protected:
			void addExtension    (const char* szExtension);
			void removeExtension (const char* szExtension);

		private:
			std::vector<char *> extensions_;
	};

	class Driver : public Epsilon3D::Driver
	{
		public:
			struct Version {
				char  major;
				char  minor;
				char  release;       // Optional: 0 if not present
				char* vendor_string; // Optional: NULL if not present
			};

			struct StackLimits {
				GLint max_tex_;         // At least 2
				GLint max_modelview_;   // At least 16
				GLint max_projection_;  // At least 2

				/// TODO
				GLint max_attrib_;
				GLint max_name_;
				GLint max_client_attrib_;
			};

		public:
			Driver (void) {
				version_.major         = -1;
				version_.minor         = -1;
				version_.release       =  0;  // Release is optional, default to 0.
				version_.vendor_string = NULL;
			}

			~Driver (void) {
				if (version_.vendor_string != NULL) {
					delete [] version_.vendor_string;
					version_.vendor_string = NULL;
				}

				version_.major   = -1;
				version_.minor   = -1;
				version_.release =  0;
			}

			/// Initialize the Driver...
			void Init (void);


			void EnableState  (State name);
			void DisableState (State name);

			void EnableLight  (int iLight);
			void DisableLight (int iLight);


			///
			/// Get Implementation Info
			///
			const char* getVendor     (void) const;
			const char* getDeviceName (void) const;

			const ExtList* getExtensions (void);
			const Version* getVersion    (void);
			
		private:
			ExtList     extension_;
			Version     version_;
			StackLimits stack_;
	};
};
}

//#include "device.h"

//class e3dRenderAPI;
//class e3dRenderDevice;
//class e3dRenderBackend;

/*
class e3dDriver
{
public:
  static e3dDisplayDevices GetDevices (void);

private:
  e3dRenderDevice*  device;
  e3dRenderBackend* backend;
};

namespace Epsilon3D {
  e3dDriver* CreateDriver (e3dRenderAPI* api, e3dRenderDevice* dev);
};
*/

namespace e3dRenderStates
{
  enum ShadingModel {
    Flat,
    Smooth,

    //Phong
  };

  enum TestFunction {
    Less,
    LessOrEqual,

    Greater,
    GreaterOrEqual,

    Equal,
    NotEqual,

    Always,
    Never
  };

  enum Winding {
    Clockwise,
    CounterClockwise
  };

  enum Side {
    Front,
    Back
  };

  enum Boolean {
    Enabled  = 1,
    Disabled = 0,

    True  = 1,
    False = 0,

    Yes = 1,
    No  = 0
  };

  enum HintMode {
    DontCare,
    Fastest,
    Nicest,
  };

  enum MatrixMode {
    Projection,
    ModelView,
    Texture,

		NumMatrixModes
  };

  enum PrimitiveType {
    Points,  
    PointSprite,

    Lines,
    LineLoop,
    LineStrip,

    Triangles,
    TriangleStrip,
    TriangleFan,

    Quads,
    QuadStrip,

    Polygon,
  };

  enum TextureType {
    Tex1D,
    Tex2D,
    Tex3D,
    CubeMap,
  };

  enum ShaderType {
    Vertex,
    Pixel
  };

  enum TextureFormat {
    RGB,
    RGBA,
    BGRA,
  };

  enum TextureCompression {
    DXT1,
    DXT3,
    DXT5,
  };

  enum StateName {
    AlphaFunc,
    AlphaTest,

    ClearColor,
    ClearDepth,
    ClearStencil,

    Color,

    DepthOffset,
    DepthFunc,
    DepthTest,

    Normalization,

    StencilFail,
    StencilFunc,
    StencilPassDepthFail,
    StencilPassDepthPass,
    StencilTest,
  };

  enum BufferName {
    ColorBuffer   = 0x01,
    DepthBuffer   = 0x02,
    StencilBuffer = 0x04,
    AccumBuffer   = 0x08
  };
}

class e3dRenderState {
public:
	e3dRenderState (void);

  void SetAlphaFunc      (e3dRenderStates::TestFunction func, float ref);
  void SetAlphaTest      (e3dRenderStates::Boolean      test);
  void SetCullFaces      (e3dRenderStates::Boolean      cull);
  void SetCullSide       (e3dRenderStates::Side         side);
  void SetFrontFace      (e3dRenderStates::Winding      face); 
  void SetPerspective    (e3dRenderStates::HintMode     mode);
  void SetClearColor     (float r, float g, float b, float a);
  void SetClearDepth     (float depth);
  void SetClearStencil   (int   stencil);
  void SetShadeModel     (e3dRenderStates::ShadingModel model);
  void SetDepthFunc      (e3dRenderStates::TestFunction func);
  void SetDepthTest      (e3dRenderStates::Boolean      test);

	void SetMatrixMode     (e3dRenderStates::MatrixMode   mode);

	void LoadIdentity      (void);
	void LoadIdentity      (e3dRenderStates::MatrixMode   mode);

	void LoadMatrix        (float                         fMatrix [16]);
	void LoadMatrix        (e3dRenderStates::MatrixMode   mode,
									        float                         fMatrix [16]);

  float* GetClearColor   (void);
  float  GetClearDepth   (void);
  int    GetClearStencil (void);

  void Clear             (int    buffers);

private:
  e3dRenderStates::ShadingModel  ShadingModel;
  e3dRenderStates::MatrixMode    MatrixMode;

  e3dRenderStates::TestFunction  DepthFunction;
  e3dRenderStates::Boolean       DepthOffset;
  // TODO: Add Two Depth Offset Vals
  e3dRenderStates::Boolean       DepthTest;
  e3dRenderStates::Boolean       DepthWrite;

  e3dRenderStates::TestFunction  AlphaFunction;
  float                          AlphaRefVal;
  e3dRenderStates::Boolean       AlphaTest;

  float                          ClearColor [4];
  float                          ClearDepth;
  int                            ClearStencil;

	float                          Matrices [e3dRenderStates::NumMatrixModes][16];

  e3dRenderStates::Boolean       CullFaces;
  e3dRenderStates::Side          CullSide;

  e3dRenderStates::Winding       FrontFace;

  e3dRenderStates::HintMode      PerspectiveCorrection;
};

#endif /* __EPSILON_3D__DRIVER_H__ */
